package com.wanban.downloader.utils

import android.text.TextUtils
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.blankj.utilcode.util.CloseUtils
import com.wanban.downloader.exception.HttpConnectionException
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.NoRouteToHostException
import java.net.URL
import java.security.cert.X509Certificate
import java.util.zip.GZIPInputStream
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

class HttpConnectUtils {

    private val allowGzip = true
    private val keepPostFor302Redirects = true
    private val allowCrossProtocolRedirects = true
    private var inputStream: InputStream? = null
    private var connection: HttpURLConnection? = null


    fun open(uri: String, httpHeaders: Map<String, String?>? = null): InputStream? {
        try {
            this.connection = makeConnection(uri, httpHeaders)
            val responseCode = connection?.responseCode?:0
            // Check for a valid response code.
            if (responseCode < 200 || responseCode > 299) {
                closeConnectionQuietly()
                throw HttpConnectionException("由从数据绑定中读取数据引起的")
            }
            // 检查有效的内容类型。
            val isCompressed = isCompressed(connection)
            inputStream = connection?.inputStream
            try {
                if (isCompressed) {
                    inputStream = GZIPInputStream(inputStream)
                }
            } catch (e: IOException) {
                closeConnectionQuietly()
                throw HttpConnectionException(e)
            }
            return inputStream
        } catch (e: IOException) {
            closeConnectionQuietly()
            throw HttpConnectionException(e)
        }
    }

    @Throws(IOException::class)
    fun compressInputStream(connection: HttpURLConnection): InputStream? {
        // 检查有效的内容类型。
        val isCompressed = isCompressed(connection)
        inputStream = connection.inputStream
        if (isCompressed) {
            inputStream = GZIPInputStream(inputStream)
        }
        return inputStream
    }

    fun close() {
        MyThreadUtils.executeSingleTask {
            CloseUtils.closeIO(inputStream)
            inputStream = null
            return@executeSingleTask true
        }
        closeConnectionQuietly()
    }

    /**
     * Establishes a connection, following redirects to do so where permitted.
     */
    @Throws(Exception::class)
    fun makeConnection(uri: String, httpRequestHeaders: Map<String, String?>? = null): HttpURLConnection {
        var url = URL(uri)
        if (!allowCrossProtocolRedirects && !keepPostFor302Redirects) {
            // HttpURLConnection不允许跨协议重定向，但以其他方式自动执行重定向。这是我们想要的行为，所以使用它。
            return makeConnection(url, /* followRedirects= */true, httpRequestHeaders)
        }
        // 我们需要自己处理重定向，以允许跨协议重定向或保留302的POST请求方法。
        var redirectCount = 0
        while (redirectCount++ <= Constants.MAX_REDIRECTS) {
            val connection = makeConnection(url, /* followRedirects= */false, httpRequestHeaders)
            val responseCode = connection.responseCode
            val location = connection.getHeaderField("Location")
            if (responseCode == HttpURLConnection.HTTP_MULT_CHOICE || responseCode == HttpURLConnection.HTTP_MOVED_PERM
                || responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_SEE_OTHER
                || responseCode == Constants.HTTP_STATUS_TEMPORARY_REDIRECT || responseCode == Constants.HTTP_STATUS_PERMANENT_REDIRECT) {
                connection.disconnect()
                url = handleRedirect(URL(uri), location)
                continue
            }
            return connection
        }
        throw HttpConnectionException(NoRouteToHostException("Too many redirects: $redirectCount"))
    }

    /**
     * Configures a connection and opens it.
     *
     * @param url               The url to connect to.
     * @param followRedirects   Whether to follow redirects.
     * @param requestParameters parameters (HTTP headers) to include in request.
     */
    private fun makeConnection(url: URL, followRedirects: Boolean, requestParameters: Map<String, String?>?): HttpURLConnection {
        trustAllHttpsCertificates()
        val connection = openConnection(url)
        connection.connectTimeout = Constants.DEFAULT_CONNECT_TIMEOUT_MILLIS
        connection.readTimeout = Constants.DEFAULT_READ_TIMEOUT_MILLIS
        val requestHeaders: MutableMap<String?, String?> = HashMap()
        if (!requestParameters.isNullOrEmpty()) {
            requestHeaders.putAll(requestParameters)
        }
        for ((key, value) in requestHeaders) {
            connection.setRequestProperty(key, value)
        }
        connection.setRequestProperty(Constants.ACCEPT_ENCODING, if (allowGzip) "gzip" else "identity")
        connection.setRequestProperty(Constants.CONTENT_TYPE, "application/json;charset=UTF-8")
        connection.setRequestProperty(Constants.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
        connection.instanceFollowRedirects = followRedirects
        connection.requestMethod = "GET"
        connection.connect()
        return connection
    }

    /**
     * 信任所有的https证书
     */
    private fun trustAllHttpsCertificates() {
        try {
            val trustAllCerts = arrayOfNulls<TrustManager>(1)
            val tm: TrustManager = MyTrustManager()
            trustAllCerts[0] = tm
            val sc = SSLContext.getInstance("SSL")
            sc.init(null, trustAllCerts, null)
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
            HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun getDocumentSize(contentRangeHeader: String?): Long {
        if (TextUtils.isEmpty(contentRangeHeader)) {
            return Constants.LENGTH_UNSET
        }
        val matcher = Constants.CONTENT_RANGE_WITH_SIZE.matcher(contentRangeHeader!!)
        val result = matcher.group(1) ?: return -1
        return if (matcher.matches()) result.toLong() else Constants.LENGTH_UNSET
    }

    private val mHostnameVerifier = HostnameVerifier { _: String?, _: SSLSession? -> true }

    private inner class MyTrustManager : TrustManager, X509TrustManager {
        override fun getAcceptedIssuers(): Array<X509Certificate>? {
            return null
        }

        fun isServerTrusted(certs: Array<X509Certificate?>?): Boolean {
            return true
        }

        fun isClientTrusted(certs: Array<X509Certificate?>?): Boolean {
            return true
        }

        override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {
            return
        }

        override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {
            return
        }
    }

    /**
     * Creates an [HttpURLConnection] that is connected with the `url`.
     */
    @VisibleForTesting
    @Throws(IOException::class)
    fun  /* package */openConnection(url: URL): HttpURLConnection {
        return url.openConnection() as HttpURLConnection
    }

    /**
     * Handles a redirect.
     *
     * @param originalUrl The original URL.
     * @param location    The Location header in the response. May be `null`.
     * @return The next URL.
     * @throws HttpConnectionException If redirection isn't possible.
     */
    @Throws(HttpConnectionException::class)
    private fun handleRedirect(originalUrl: URL, location: String?): URL {
        if (location == null) {
            throw HttpConnectionException("Null location redirect")
        }
        // Form the new url.
        val url = try {
            URL(originalUrl, location)
        } catch (e: MalformedURLException) {
            throw HttpConnectionException(e)
        }

        // Check that the protocol of the new url is supported.
        val protocol = url.protocol
        if ("https" != protocol && "http" != protocol) {
            throw HttpConnectionException("Unsupported protocol redirect: $protocol")
        }
        if (!allowCrossProtocolRedirects && protocol != originalUrl.protocol) {
            throw HttpConnectionException("Disallowed cross-protocol redirect (" + originalUrl.protocol + " to " + protocol + ")")
        }
        return url
    }

//    /**
//     * 尝试完全跳过指定的字节数。
//     *
//     * @param bytesToSkip The number of bytes to skip.
//     * @throws IOException If the thread is interrupted during the operation, or if the data ended
//     * before skipping the specified number of bytes.
//     */
//    @Throws(IOException::class)
//    private fun skipFully(bytesToSkip: Long) {
//        var bytesToSkip = bytesToSkip
//        if (bytesToSkip == 0L) {
//            return
//        }
//        if (inputStream == null) return
//        val skipBuffer = ByteArray(4096)
//        while (bytesToSkip > 0) {
//            val readLength = Math.min(bytesToSkip, skipBuffer.size.toLong()).toInt()
//            val read = inputStream!!.read(skipBuffer, 0, readLength)
//            if (Thread.currentThread().isInterrupted) {
//                throw HttpConnectionException(InterruptedIOException())
//            }
//            if (read == -1) {
//                throw HttpConnectionException("由从数据绑定中读取数据引起的。")
//            }
//            bytesToSkip -= read.toLong()
//            //TODO 通知开始传输字节 read
////            bytesTransferred(read);
//        }
//    }



    /**
     * Closes the current connection quietly, if there is one.
     */
    private fun closeConnectionQuietly() {
        try {
            connection?.disconnect()
            connection = null
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    fun isCompressed(connection: HttpURLConnection?): Boolean {
        val contentEncoding = connection!!.getHeaderField("Content-Encoding")
        return "gzip".equals(contentEncoding, ignoreCase = true)
    }

    /**
     * 将整个[InputStream]转换为字节数组。
     *
     * @param inputStream the [InputStream] to be read. The input stream is not closed by this method.
     * @return a byte array containing all of the inputStream's bytes.
     * @throws IOException if an error occurs reading from the stream.
     */
    @Throws(IOException::class)
    fun toByteArray(inputStream: InputStream): ByteArray {
        val buffer = ByteArray(1024 * 4)
        val outputStream = ByteArrayOutputStream()
        var bytesRead: Int
        while (inputStream.read(buffer).also { bytesRead = it } != -1) {
            outputStream.write(buffer, 0, bytesRead)
        }
        return outputStream.toByteArray()
    }

    /**
     * 尝试从相应的响应头中解析响应主体的长度。
     */
    fun getContentLength(contentLengthHeader: String?, contentRangeHeader: String?): Long {
        var contentLength = Constants.LENGTH_UNSET
        if (!TextUtils.isEmpty(contentLengthHeader)) {
            try {
                contentLength = contentLengthHeader?.toLongOrNull()?:0
            } catch (e: NumberFormatException) {
                Log.e(TAG, "Unexpected Content-Length [$contentLengthHeader]")
            }
        }
        if (!TextUtils.isEmpty(contentRangeHeader)) {
            val matcher = Constants.CONTENT_RANGE_WITH_START_AND_END.matcher(contentRangeHeader!!)
            if (matcher.matches()) {
                try {
                    val contentLengthFromRange = checkNotNull(matcher.group(2)).toLong() - checkNotNull(matcher.group(1)).toLong() + 1
                    if (contentLength < 0) {
                        // Some proxy servers strip the Content-Length header. Fall back to the length
                        // calculated here in this case.
                        contentLength = contentLengthFromRange
                    } else if (contentLength != contentLengthFromRange) {
                        // If there is a discrepancy between the Content-Length and Content-Range headers,
                        // assume the one with the larger value is correct. We have seen cases where carrier
                        // change one of them to reduce the size of a request, but it is unlikely anybody would
                        // increase it.
                        Log.w(TAG, "Inconsistent headers [$contentLengthHeader] [$contentRangeHeader]")
                        contentLength = Math.max(contentLength, contentLengthFromRange)
                    }
                } catch (e: NumberFormatException) {
                    Log.e(TAG, "Unexpected Content-Range [$contentRangeHeader]")
                }
            }
        }
        return contentLength
    }


    fun buildRangeRequestHeader(position: Long, length: Long): String? {
        if (position == 0L && (length == Constants.LENGTH_UNSET || length == 0L)) {
            return null
        }
        val rangeValue = StringBuilder()
        rangeValue.append("bytes=")
        rangeValue.append(position)
        rangeValue.append("-")
        if (length != Constants.LENGTH_UNSET) {
            rangeValue.append(position + length - 1)
        }
        return rangeValue.toString()
    }

    companion object {
        const val TAG = "HttpConnection"
    }
}